package com.demo.main.utils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

public class ServletUtil {

    public static final String PREFIX = "/WEB-INF/views/", SUFFIX = ".jsp";
    public static final String LOGIN_PATH = "/login", REGISTER_PATH = "/register";

    // permission
    public static final String API_PATH = "/api";
    public static final String PERMISSION_PATH_ADMIN = API_PATH + "/admin",
            PERMISSION_PATH_USER = API_PATH + "/user",
            PERMISSION_PATH_PUBLIC = API_PATH + "/public";


    public static final String DEFAULT_SESSION_USER = "session_user",
            DEFAULT_SESSION_USERNAME = "session_username",
            DEFAULT_SESSION_USERID = "session_userid",
            DEFAULT_SESSION_ROLE = "session_role",
            DEFAULT_IS_LOGIN = "session_is_login";

    public static final String PATH_404 = "/404", PATH_500 = "/500";

    public static final Map<String, String> roleHomeViewMap = new HashMap<String, String>() {{
        put("user", "/user/view/notice");
        put("admin", "/admin/view/book_management");
    }};

    private final HttpServletRequest request;
    private final HttpServletResponse response;
    private final HttpSession session;

    public ServletUtil(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
        session = request.getSession();
    }

    public ServletUtil(ServletRequest request, ServletResponse response) {
        this((HttpServletRequest) request, (HttpServletResponse) response);
    }

    // 参数转换
    public static <T> T convertRequestParameter(HttpServletRequest request, Class<T> clazz) {
        T entity;
        try {
            entity = clazz.getDeclaredConstructor().newInstance();
            for (Field field : getAllFields(clazz)) {
                field.setAccessible(true);

                String parameter = request.getParameter(field.getName());
                Object value;
                Class<?> fieldType = field.getType();

                if (parameter == null || parameter.trim().isEmpty()) {
                    if (!fieldType.isPrimitive()) {
                        field.set(entity, null);
                    }
                    continue;
                }

                value = convertStringToType(parameter, fieldType);
                field.set(entity, value);
            }
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException |
                 InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        return entity;
    }

    private static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    private static Object convertStringToType(String value, Class<?> type) {
        if (type.equals(String.class)) {
            return value.trim();
        } else if (type.equals(Integer.class) || type.equals(int.class)) {
            return Integer.parseInt(value);
        } else if (type.equals(Long.class) || type.equals(long.class)) {
            return Long.parseLong(value);
        } else if (type.equals(Double.class) || type.equals(double.class)) {
            return Double.parseDouble(value);
        } else if (type.equals(Float.class) || type.equals(float.class)) {
            return Float.parseFloat(value);
        } else if (type.equals(Boolean.class) || type.equals(boolean.class)) {
            return Boolean.parseBoolean(value);
        } else if (type.equals(Short.class) || type.equals(short.class)) {
            return Short.parseShort(value);
        } else if (type.equals(Byte.class) || type.equals(byte.class)) {
            return Byte.parseByte(value);
        } else if (type.equals(Character.class) || type.equals(char.class)) {
            if (value.length() == 1) {
                return value.charAt(0);
            } else {
                throw new IllegalArgumentException("Invalid character value: " + value);
            }
        } else if (type.equals(BigDecimal.class)) {
            return new BigDecimal(value);
        } else if (type.equals(Timestamp.class)) {
            return DateUtil.stringToTimestamp(value);
        } else if (type.equals(Date.class)) {
            return DateUtil.stringToDate(value);
        } else if (type.equals(LocalDateTime.class)) {
            return DateUtil.stringToLocalDateTime(value);
        } else if (type.equals(LocalDate.class)) {
            return DateUtil.stringToLocalDate(value);
        } else {
            throw new IllegalArgumentException("Unsupported type: " + type.getName());
        }
    }

    public static String wrap(String path) {
        return PREFIX + path + SUFFIX;
    }

    public static void forward(String path, HttpServletRequest request, HttpServletResponse response) {
        try {
            request.getRequestDispatcher(path).forward(request, response);
        } catch (Exception e) {
            throw new RuntimeException("转发失败" + e);
        }
    }

    public static void redirect(String path, HttpServletRequest request, HttpServletResponse response) {
        try {
            response.sendRedirect(request.getContextPath() + path);
        } catch (Exception e) {
            throw new RuntimeException("重定向失败" + e);
        }
    }

    public static void forwardToView(String path, HttpServletRequest request, HttpServletResponse response) {
        forward(wrap(path), request, response);
    }

    // 路径跳转

    public static void redirectToView(String path, HttpServletRequest request, HttpServletResponse response) {
        redirect(wrap(path), request, response);
    }

    public static void redirectToLogin(HttpServletRequest request, HttpServletResponse response) {
        redirect(LOGIN_PATH, request, response);
    }

    public static void redirectToRegister(HttpServletRequest request, HttpServletResponse response) {
        redirect(REGISTER_PATH, request, response);
    }

    // session会话管理
    public static <T> void setSessionAttribute(HttpServletRequest request, String name, T value) {
        request.getSession().setAttribute(name, value);
    }

    public static Object getSessionAttribute(HttpServletRequest request, String name) {
        return request.getSession().getAttribute(name);
    }

    @SuppressWarnings("unchecked")
    public static <T> T getSessionAttribute(HttpServletRequest request, String name, Class<T> clazz) {
        return (T) request.getSession().getAttribute(name);
    }

    public static void removeSessionAttribute(HttpServletRequest request, String name) {
        request.getSession().setAttribute(name, null);
    }

    // 异常处理
    public static void handleException(HttpServletResponse response, String message, Exception e) {
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        try {
            response.getWriter().println(message);
            if (e != null) {
                response.getWriter().println(e);
            }
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    public static void handleException(HttpServletResponse response, String message) {
        handleException(response, message);
    }

    // Cookie操作
    public static void addCookie(HttpServletResponse response, String name, String value, int maxAge, boolean isSecure) {
        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(maxAge);
        cookie.setHttpOnly(true);
        if (isSecure) {
            cookie.setSecure(true);
        }
        response.addCookie(cookie);
    }

    public static String getCookieValue(HttpServletRequest request, String name) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    public static void deleteCookie(HttpServletResponse response, String name) {
        addCookie(response, name, "", 0, false);
    }

    public HttpServletRequest getHttpServletRequest() {
        return request;
    }

    public ServletResponse getServletResponse() {
        return response;
    }

    public String getRequestParameterAction() {
        return request.getParameter("action");
    }

    public <T> T convertRequestParameter(Class<T> tClass) {
        return convertRequestParameter(request, tClass);
    }

    public void forward(String path) {
        forward(path, request, response);
    }

    public void redirect(String path) {
        redirect(path, request, response);
    }

    public void redirectWithApiPath(String path) {
        redirect(API_PATH + path, request, response);
    }

    public void forwardToView(String path) {
        forward(wrap(path));
    }

    public void redirectToView(String path) {
        redirect(wrap(path));
    }

    public void redirectToLogin() {
        redirect(LOGIN_PATH);
    }

    public void redirectToRegister() {
        redirect(REGISTER_PATH);
    }

    public <T> void setSessionAttribute(String name, T value) {
        session.setAttribute(name, value);
    }

    public Object getSessionAttribute(String name) {
        return session.getAttribute(name);
    }

    @SuppressWarnings("unchecked")
    public <T> T getSessionAttribute(String name, Class<T> clazz) {
        return (T) session.getAttribute(name);
    }

    public void removeSessionAttribute(String name) {
        session.setAttribute(name, null);
    }

    public void setContentType(String contentType) {
        response.setContentType(contentType);
    }


    @SuppressWarnings("unchecked")
    public <T> T getSessionLoginUser(Class<T> clazz) {
        return (T) session.getAttribute(DEFAULT_SESSION_USER);
    }

    public <T> void setSessionLoginUser(T user) {
        session.setAttribute(DEFAULT_SESSION_USER, user);
    }

    public String getSessionUsername() {
        return (String) session.getAttribute(DEFAULT_SESSION_USERNAME);
    }

    public void setSessionUsername(String username) {
        session.setAttribute(DEFAULT_SESSION_USERNAME, username);
    }

    public <T> void setSessionUsername(T username) {
        session.setAttribute(DEFAULT_SESSION_USERNAME, username);
    }

    @SuppressWarnings("unchecked")
    public <T> T getSessionUsername(Class<T> clazz) {
        return (T) session.getAttribute(DEFAULT_SESSION_USERNAME);
    }

    // for DEFAULT_SESSION_USERID
    public String getSessionUserid() {
        return (String) session.getAttribute(DEFAULT_SESSION_USERID);
    }

    public void setSessionUserid(String userid) {
        session.setAttribute(DEFAULT_SESSION_USERID, userid);
    }

    public <T> void setSessionUserid(T userId) {
        session.setAttribute(DEFAULT_SESSION_USERID, userId);
    }

    @SuppressWarnings("unchecked")
    public <T> T getSessionUserId(Class<T> clazz) {
        return (T) session.getAttribute(DEFAULT_SESSION_USERID);
    }

    // for DEFAULT_SESSION_ROLE
    public String getSessionRole() {
        return (String) session.getAttribute(DEFAULT_SESSION_ROLE);
    }

    public void setSessionRole(String role) {
        session.setAttribute(DEFAULT_SESSION_ROLE, role);
    }

    public <T> void setSessionRole(T role) {
        session.setAttribute(DEFAULT_SESSION_ROLE, role);
    }

    @SuppressWarnings("unchecked")
    public <T> T getSessionRole(Class<T> clazz) {
        return (T) session.getAttribute(DEFAULT_SESSION_ROLE);
    }

    public void setSessionUseridAndUsernameAndRole(Object userid, String username, String role) {
        setSessionUsername(username);
        setSessionRole(role);
        setSessionUserid(userid);
    }

    public void login(Object userid, String username, String role) {
        setSessionUseridAndUsernameAndRole(userid, username, role);
        setSessionAttribute(DEFAULT_IS_LOGIN, true);
    }

    public void loginAndRedirectToHomeViewByCurrentRole(Object userid, String username, String role) {
        login(userid, username, role);
        redirectToHomeViewByCurrentRole();
    }

    public void login(String username, String role) {
        login(null, username, role);
    }

    public <T> void login(T loginUser) {
        setSessionLoginUser(loginUser);
        setSessionAttribute(DEFAULT_IS_LOGIN, true);
    }

    public void removeSessionLoginInfo() {
        removeSessionAttribute(DEFAULT_SESSION_USER);
        removeSessionAttribute(DEFAULT_SESSION_ROLE);
        removeSessionAttribute(DEFAULT_SESSION_USERID);
        removeSessionAttribute(DEFAULT_SESSION_USERNAME);
        removeSessionAttribute(DEFAULT_IS_LOGIN);
        setSessionAttribute(DEFAULT_IS_LOGIN, false);
    }

    public void logout() {
        removeSessionLoginInfo();
    }

    public void logoutAndRedirectToLoginView() {
        logout();
        redirectToLogin();
    }

    public boolean isLogin() {
        Object isLogin = getSessionAttribute(DEFAULT_IS_LOGIN);
        if (isLogin == null) {
            return false;
        }
        return (boolean) isLogin;
    }

    public void redirectToHomeViewByRole(String role) {
        if (role == null) {
            throw new NullPointerException("role cannot be empty");
        }
        redirectWithApiPath(roleHomeViewMap.get(role));
    }

    public void redirectToHomeViewByCurrentRole() {
        redirectToHomeViewByRole(getSessionRole());
    }

    public String getServletPath() {
        return request.getServletPath();
    }

    public void redirectTo404() {
        redirect(PATH_404);
    }

    public void redirectTo500() {
        redirect(PATH_500);
    }

    public <T> T getParameterAs(String paramName, Class<T> type) {
        String paramValue = request.getParameter(paramName);
        if (paramValue == null) {
            return null;
        }

        try {
            if (type.equals(String.class)) {
                return type.cast(paramValue);
            } else if (type.equals(Integer.class) || type.equals(int.class)) {
                return type.cast(Integer.parseInt(paramValue));
            } else if (type.equals(Long.class) || type.equals(long.class)) {
                return type.cast(Long.parseLong(paramValue));
            } else if (type.equals(Double.class) || type.equals(double.class)) {
                return type.cast(Double.parseDouble(paramValue));
            } else if (type.equals(Float.class) || type.equals(float.class)) {
                return type.cast(Float.parseFloat(paramValue));
            } else if (type.equals(Boolean.class) || type.equals(boolean.class)) {
                return type.cast(Boolean.parseBoolean(paramValue));
            } else if (type.equals(Timestamp.class)) {
                return type.cast(DateUtil.stringToTimestamp(paramValue));
            } else if (type.equals(Date.class)) {
                return type.cast(DateUtil.stringToDate(paramValue));
            } else if (type.equals(LocalDateTime.class)) {
                return type.cast(DateUtil.stringToLocalDateTime(paramValue));
            } else if (type.equals(BigDecimal.class)) {
                return type.cast(new BigDecimal(paramValue));
            } else if (type.equals(LocalDate.class)) {
                return type.cast(DateUtil.stringToLocalDate(paramValue));
            } else {
                throw new IllegalArgumentException("Unsupported type: " + type.getName());
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to convert parameter '" + paramName + "' to type " + type.getName(), e);
        }
    }

    public <T> T getParameterId(Class<T> type) {
        return getParameterAs("id", type);
    }

    public Integer getParameterIntegerId() {
        return getParameterAs("id", Integer.class);
    }

    public Long getParameterLongId() {
        return getParameterAs("id", Long.class);
    }

    public String getParameterStringId() {
        return getParameterAs("id", String.class);
    }

    public ServletUtil setAttribute(String name, Object value) {
        request.setAttribute(name, value);
        return this;
    }

    public void jsonSuccess(String message) throws IOException {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("message", message);
        resultMap.put("isSuccess", true);
        response.getWriter().println(JSONUtil.mapToJson(resultMap));
    }

    public void jsonDeleteSuccess() throws IOException {
        jsonSuccess("删除成功");
    }

    /**
     * 处理分页查询和条件筛选。
     *
     * @param conditionPageCountSupplier 用于获取符合条件的记录总页数的函数接口
     * @param conditionEntity            封装查询条件的数据对象
     * @param listSupplier               用于执行分页查询的函数接口。该接口接收两个参数：第一个参数为查询条件数据对象，第二个参数为目标页码。接口返回查询结果集。
     * @param path                       查询完成后的转发路径
     * @param <T>                        条件数据对象的泛型类型
     *                                   <p>
     *                                   该方法执行以下操作：
     *                                   <p>
     *                                   1. 获取当前请求的页码
     *                                   2. 获取符合条件的记录总页数
     *                                   3. 校验并调整页码的有效性
     *                                   4. 执行分页查询并获取结果集
     *                                   5. 设置查询结果和分页信息
     *                                   6. 转发到指定路径
     */
    public <T> void handlePaginationAndCondition(
            Function<T, Integer> conditionPageCountSupplier,
            T conditionEntity,
            BiFunction<T, Integer, Object> listSupplier,
            String path) {

        Integer pageNum = getParameterAs("pageNum", Integer.class);
        Integer pageCount = conditionPageCountSupplier.apply(conditionEntity);

        pageNum = PaginationUtil.getValidPageNum(pageNum, pageCount);

        setAttribute("list", listSupplier.apply(conditionEntity, pageNum))
                .setAttribute("pageCount", pageCount)
                .forward(path);
    }

    /**
     * 为请求设置属性，但仅在指定的属性不存在时才进行设置。
     * <p>
     * 该方法接受可变数量的参数，这些参数应该以键值对的形式传入。
     * 对于每一对参数，第一个被视为键（必须能转换为字符串），第二个被视为值。
     * 如果请求中不存在该键对应的属性，则会将该值设置为请求的该键对应的属性。
     *
     * @param keyValuePairs 键值对参数列表，应为偶数个参数，奇数位置的参数为键，偶数位置的参数为值
     * @throws IllegalArgumentException 如果参数个数为奇数，或者任何键为null
     */
    public void setAttributesIfAbsent(Object... keyValuePairs) {
        if (keyValuePairs.length % 2 != 0) {
            throw new IllegalArgumentException("必须传入偶数个参数");
        }
        for (int i = 0; i < keyValuePairs.length; i += 2) {
            if (keyValuePairs[i] == null) {
                throw new IllegalArgumentException("键不能为null");
            }
            String key = keyValuePairs[i].toString();
            Object value = keyValuePairs[i + 1];
            if (request.getAttribute(key) == null) {
                request.setAttribute(key, value);
            }
        }
    }
}
